跳到主要内容

Unions and interfaces

Unioninterface 是抽象类型,它们允许一个 schema 字段,返回多个类型中的某一种类型。

Union

当你定义一个 union 类型时,你声明的对象类型可以被包含在 union 中。

union Media = Book | Movie

一个字段可以将一个 union 类型(或者一个 union 类型的列表),作为它的返回值类型。它可以返回任一被包含在 union 中的对象类型。

type Query {
allMedia: [Media] # This list can include both Book and Movie objects
}

union 类型所包含的所有类型,必须是对象类型(不能是 scalar、input 类型)。被包含的类型之间,不需要共享任何字段。

私货:被包含的类型之间,可以是毫无关联的类型,字段不需要有相似性。

例子

下面的 schema 定义了一个名为SearchResult的 union 类型。它可以返回一本Book或一位Author

union SearchResult = Book | Author

type Book {
title: String!
}

type Author {
name: String!
}

type Query {
search(contains: String): [SearchResult!]
}

SearchResult类型允许Query.search返回一个包含Books 和Authors 的列表。

查询一个 union

如果一个返回值的类型,是 union 时,GraphQL 客户端不知道字段将返回哪个对象类型。为此,一个查询可以包含多种可能类型的子字段。

下面是一个查询 schema 的示例:

query GetSearchResults {
search(contains: "Shakespeare") {
# Querying for __typename is almost always recommended,
# but it's even more important when querying a field that
# might return one of multiple types.
__typename
... on Book {
title
}
... on Author {
name
}
}
}

什么是__typename字段?

这个查询使用 inline fragment 语法去查询SearchResulttitle(如果他是Book)或name(如果他是Author)。web 客户端可以被 passing the possibleTypes option告知关于这个多态的关系。

私货:passing possibleTypes option 是个啥?

下面是前面查询的返回结果:

{
"data": {
"search": [
{
"__typename": "Book",
"title": "The Complete Works of William Shakespeare"
},
{
"__typename": "Author",
"name": "William Shakespeare"
}
]
}
}

解析一个 union

阅读本章节前,建议先了解resolver

为了全面解析 union,Apollo Server 需要确认 union 将会返回哪一种类型。为此,你需要在 resolver map 中,为 union 定义一个__resolveType函数。

__resolveType函数负责确定一个对象的 GraphQL 返回类型并且以字符串的形式返回类型名称。可以通过任意逻辑来完成这件事,例如:

  • 检查字段上的某具有唯一特征的值是否存在
  • 如果 JavaScript 对象的类型与 GraphQL 的类型相关连,则使用instanceOf

下面是一个为SearchResult定义的__resovleType函数:

const resolvers = {
SearchResult: {
__resolveType(obj, contextValue, info){
// Only Author has a name field
if(obj.name){
return 'Author';
}
// Only Book has a title field
if(obj.title){
return 'Book';
}
return null; // GraphQLError is thrown
},
},
Query: {
search: () => { ... }
},
};

const server = new ApolloServer({
typeDefs,
resolvers,
});

const { url } = await startStandaloneServer(server);

console.log(`🚀 Server ready at: ${url}`);

如果__resolveType函数返回一个与 schema 类型定义不相符的值,与其相关的 operation 将产生一个 GraphQL 错误。

interface

一个 interface 确定了多个对象类型都可以包含的一批字段:

interface Book {
title: String!
author: Author!
}

如果一个对象类型implements了一个 interface,它一定可以具备 interface 所有的字段:

type Textbook implements Book {
title: String! # Must be present
author: Author! # Must be present
courses: [Course!]!
}

一个字段可以将一个 interface(或者一个 interface 的列表)作为返回类型。在这种情况下,这个字段可以返回任意一个implements了这个 interface 的对象类型:

type Query {
books: [Book!]! # Can include Textbook objects
}

例子

下面的 schema 中定义了名为Book的 interface,并且有两个对象类型 implements 了它:

interface Book {
title: String!
author: Author!
}

type Textbook implements Book {
title: String!
author: Author!
courses: [Course!]!
}

type ColoringBook implements Book {
title: String!
author: Author!
colors: [String!]!
}

type Query {
books: [Book!]!
}

在这个 scheme 中,Query.books可以返回包含TextbookColoringBook的列表。

查询一个 interface

如果一个字段的类型是 interface,客户端可以查询这个 interface 的字段中的任意字段:

query GetBooks {
books {
title
author
}
}

客户端也可以查询这个 interface 没有包含的字段:

query GetBooks {
books {
# Querying for __typename is almost always recommended,
# but it's even more important when querying a field that
# might return one of multiple types.
__typename
title
... on Textbook {
courses {
# Only present in Textbook
name
}
}
... on ColoringBook {
colors # Only present in ColoringBook
}
}
}

什么是__typename 字段?

这个查询使用内联片段去查询Bookcourses字段(如果它属于Textbook类型)或者colors字段(如果它属于ColoringBook类型)。通过passing the possibleTypes option,web 客户端可以被告知关于这个多态关系。

下面是前面查询的一个有效结果:

{
"data": {
"books": [
{
"__typename": "Textbook",
"title": "Wheelock's Latin",
"courses": [
{
"name": "Latin I"
}
]
},
{
"__typename": "ColoringBook",
"title": "Oops All Water",
"colors": ["Blue"]
}
]
}
}

解析一个 interface

阅读本章节前,建议了解resolver

与 union 类型相同,Apollo Server 需求 interface 定义一个__resolveType函数去确定那个哪一种对象类型将会被返回。

下面是一个Book__resolveType函数的例子:

const resolvers = {
Book: {
__resolveType(book, contextValue, info){
// Only Textbook has a courses field
if(book.courses){
return 'Textbook';
}
// Only ColoringBook has a colors field
if(book.colors){
return 'ColoringBook';
}
return null; // GraphQLError is thrown
},
},
Query: {
books: () => { ... }
},
};